package com.cicadasmall.security.config;

import com.cicadasmall.common.constant.Constant;
import com.cicadasmall.security.LoginUserDetails;
import com.cicadasmall.security.SecurityUrlProperties;
import com.cicadasmall.security.handler.RestAuthExceptionEntryPoint;
import com.cicadasmall.security.handler.RestOauth2LogoutHandler;
import com.cicadasmall.security.handler.RestWebResponseExceptionTranslator;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.util.HashMap;
import java.util.Map;


/**
 * OAuth2Config
 *
 * @author Jin
 */
@Slf4j
@Configuration
public class OAuth2Config {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private SecurityUrlProperties securityUrlProperties;

    @Bean
    public TokenStore redisTokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisTemplate.getConnectionFactory());
        redisTokenStore.setPrefix(Constant.PREFIX + Constant.OAUTH_PREFIX);
        return redisTokenStore;
    }


    @Bean
    public RestOauth2LogoutHandler oauthLogoutHandler() {
        RestOauth2LogoutHandler restOauth2LogoutHandler = new RestOauth2LogoutHandler();
        restOauth2LogoutHandler.setTokenStore(redisTokenStore());
        return restOauth2LogoutHandler;
    }

    @Bean
    public RestWebResponseExceptionTranslator restWebResponseExceptionTranslator() {
        return new RestWebResponseExceptionTranslator();

    }

    @Configuration
    @EnableAuthorizationServer
    @AutoConfigureAfter(AuthorizationServerEndpointsConfigurer.class)
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;

        @Autowired
        private UserDetailsService userDetailsServiceImpl;

        @Autowired
        private TokenStore redisTokenStore;

        @Autowired
        private ClientDetailsService redisClientDetailsService;

        @Autowired
        private RandomValueAuthorizationCodeServices redisAuthorizationCodeService;


        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.allowedTokenEndpointRequestMethods(HttpMethod.POST)
                    .tokenStore(redisTokenStore)
                    .tokenEnhancer(tokenEnhancer())
                    .authenticationManager(authenticationManager)
                    .reuseRefreshTokens(false)
                    .userDetailsService(userDetailsServiceImpl)
                    .authorizationCodeServices(redisAuthorizationCodeService)
                    .exceptionTranslator(restWebResponseExceptionTranslator());
        }

        @Bean
        public TokenEnhancer tokenEnhancer() {
            return (accessToken, authentication) -> {
                final Map<String, Object> information = new HashMap<>(4);
                if (null != authentication.getUserAuthentication()) {
                    LoginUserDetails loginUser = ((LoginUserDetails) authentication.getUserAuthentication().getPrincipal());
                    information.put("userid", loginUser.getUid());
                    information.put("username", loginUser.getUsername());
                }
                information.put("license", Constant.OAUTH_LICENSE);
                DefaultOAuth2AccessToken defaultOAuth2AccessToken = (DefaultOAuth2AccessToken) accessToken;
                defaultOAuth2AccessToken.setAdditionalInformation(information);
                return accessToken;
            };
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.withClientDetails(redisClientDetailsService);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer serverSecurityConfigurer) {
            serverSecurityConfigurer
                    .allowFormAuthenticationForClients()
                    .tokenKeyAccess("permitAll()")
                    .checkTokenAccess("isAuthenticated()");
        }

    }

    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.authenticationEntryPoint(new RestAuthExceptionEntryPoint(objectMapper));
        }

        @Autowired
        private ObjectMapper objectMapper;

        @Override
        public void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity
                    .requestMatchers()
                    .antMatchers(securityUrlProperties.getAuthenticated())
                    .and()
                    .authorizeRequests()
                    .anyRequest()
                    .permitAll()
                    .and()
                    .cors();
        }
    }
}
